package org.stool.netty.http.download;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.EmptyByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.util.concurrent.GenericFutureListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;

public class Connector {

    private final static Logger LOG = LoggerFactory.getLogger(Connector.class);

    private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();


    public void connect(String host, int port, String uri, Handler<Throwable> connectFailHandler, Handler<HttpResponse> headersHandler, Handler<HttpContent> messageHandler) {
        Bootstrap b = new Bootstrap();
        b.group(eventLoopGroup);
        b.channel(NioSocketChannel.class);
        b.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
            }
        });

        DownloadHandler handler = new DownloadHandler(headersHandler, messageHandler);

        ChannelFuture fut = b.connect(new InetSocketAddress(host, port));
        fut.addListener(res -> {
            if (res.isSuccess()) {
                Channel ch = fut.channel();
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("codec", new HttpClientCodec());
                pipeline.addLast("hander", handler);

                sendRequest(host, uri, handler.ctx);
            } else {
                res.cause().printStackTrace();
                if (connectFailHandler != null) {
                    connectFailHandler.handle(res.cause());
                }

            }
        });

    }

    private void sendRequest(String host, String uri, ChannelHandlerContext ctx) {
        HttpHeaders httpHeaders = new DefaultHttpHeaders();
        httpHeaders.set(HttpHeaderNames.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36");
        httpHeaders.set(HttpHeaderNames.HOST, host);

        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri, Unpooled.EMPTY_BUFFER, httpHeaders, EmptyHttpHeaders.INSTANCE);

        ChannelPromise channelPromise = ctx.newPromise();
        ctx.writeAndFlush(request, channelPromise);
    }


    public static class DownloadHandler extends ChannelDuplexHandler {

        ChannelHandlerContext ctx;
        Handler<HttpResponse> headersHandler;
        Handler<HttpContent> messageHandler;

        public DownloadHandler(Handler<HttpResponse> headersHandler, Handler<HttpContent> messageHandler) {
            this.headersHandler = headersHandler;
            this.messageHandler = messageHandler;
        }

        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            this.ctx = ctx;
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            if (msg instanceof HttpResponse) {

                HttpResponse response = (HttpResponse) msg;

                if (headersHandler != null) {
                    headersHandler.handle(response);
                }


                if (response.status() != HttpResponseStatus.OK) {
                    LOG.info(response.status().toString());
//                    ctx.channel().close();
                }

            } else if (msg instanceof HttpContent) {

                HttpContent httpContent = (HttpContent) msg;

                if (messageHandler != null) {
                    messageHandler.handle(httpContent);
                }
//                LOG.info(httpContent.content().toString(Charset.forName("utf-8")));


                if (msg instanceof LastHttpContent) {
                    ctx.channel().close();
                }
            }
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            super.channelReadComplete(ctx);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            LOG.error("exception", cause);
        }
    }


    public static void main(String[] args) {
        new Connector().connect("tomcat.apache.org", 80, "/", null, null, null);
    }

}
